// Package db 实现了 bakctl 的数据库操作核心功能。
//
// 该文件提供了数据库连接管理和基础操作的核心功能，包括：
//   - 数据库连接的建立和管理
//   - 数据库表结构的定义和创建
//   - 基础的CRUD操作接口
//   - 数据库事务处理
//   - 连接池管理和优化
//
// 主要功能包括：
//   - 初始化SQLite数据库连接
//   - 创建必要的数据库表结构
//   - 提供统一的数据库操作接口
//   - 处理数据库连接的生命周期
//   - 确保数据库操作的可靠性和性能
//
// 该文件是整个数据库系统的核心，为其他模块提供稳定的数据存储服务。
package db

import (
	"fmt"
	"os"
	"path/filepath"

	"gitee.com/MM-Q/bakctl/internal/types"
	"gitee.com/MM-Q/bakctl/internal/utils"
	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

// validate 校验核心配置的合法性（确保文件名和目录路径非空）
// 初始化前调用，提前拦截无效配置，避免数据库连接失败
//
// 参数:
//   - dbFilename: 数据库文件名（含后缀）
//
// 返回值:
//   - error: 配置校验失败时返回错误信息，否则返回 nil
func validate(dbFilename string) error {
	// 校验数据库文件名非空（必须含后缀，如 .db）
	if dbFilename == "" {
		return fmt.Errorf("数据库文件名不能为空, 请指定完整文件名(例: backup_system.db)")
	}

	// 校验数据库文件名含后缀.db或.db3
	if filepath.Ext(dbFilename) != ".db" && filepath.Ext(dbFilename) != ".db3" {
		return fmt.Errorf("数据库文件名必须以 .db 或 .db3 结尾")
	}

	return nil
}

// InitSQLite 基于 DBInitConfig 初始化 SQLite 数据库
//
// 参数:
//   - dbFilename: 待初始化的数据库文件名（含后缀）
//   - dataDirPath: 数据库文件所在目录路径
//
// 返回值:
//   - *sqlx.DB: 初始化成功的数据库连接对象
//   - error: 初始化失败时返回错误信息，否则返回 nil
func InitSQLite(dbFilename string, dataDirPath string) (*sqlx.DB, error) {
	// 先校验配置合法性
	if err := validate(dbFilename); err != nil {
		return nil, fmt.Errorf("配置校验失败：%w", err)
	}

	// 如果数据目录为空，则使用默认的数据目录
	if dataDirPath == "" {
		dataDirPath = types.DataDirPath
	}

	// 确保数据目录存在（目录不存在则自动创建，权限 0755）
	if err := os.MkdirAll(dataDirPath, 0755); err != nil {
		return nil, fmt.Errorf("创建数据目录失败：%w", err)
	}

	// 确保备份目录存在（目录不存在则自动创建，权限 0755）
	if err := os.MkdirAll(types.BackupDirPath, 0755); err != nil {
		return nil, fmt.Errorf("创建备份目录失败：%w", err)
	}

	// 获取完整路径
	dbFullPath := filepath.Join(dataDirPath, dbFilename)

	// 检查数据库文件是否存在
	dbExists := true
	if _, err := os.Stat(dbFullPath); os.IsNotExist(err) {
		dbExists = false
	}

	// 连接数据库
	sqlDB, err := sqlx.Connect("sqlite3", dbFullPath)
	if err != nil {
		return nil, fmt.Errorf("连接数据库失败 (路径：%s) :%w", dbFullPath, err)
	}

	// 如果数据库文件不存在，则执行初始化脚本
	if !dbExists {
		if _, err := sqlDB.Exec(initDbScript); err != nil {
			return nil, fmt.Errorf("执行数据库初始化脚本失败：%w", err)
		}
	}

	return sqlDB, nil
}

// 初始化建库脚本
const initDbScript = `-- SQLite 数据库初始化脚本
CREATE TABLE IF NOT EXISTS backup_tasks (
    ID INTEGER PRIMARY KEY AUTOINCREMENT, -- 任务唯一标识，自增主键
    name TEXT NOT NULL UNIQUE,           -- 任务名称，唯一且非空
    retain_count INTEGER DEFAULT 3,      -- 保留备份数量(默认保留3个/每天保留3个)
    retain_days INTEGER DEFAULT 7,       -- 保留天数(默认保留7天)
    backup_dir TEXT NOT NULL,            -- 备份源目录，非空
    storage_dir TEXT NOT NULL,           -- 存储目录，非空
    compress BOOLEAN DEFAULT FALSE,      -- 是否压缩 (true/false)
    include_rules TEXT,                  -- 包含规则 (JSON数组字符串)
    exclude_rules TEXT,                  -- 排除规则 (JSON数组字符串)
    max_file_size INTEGER,               -- 最大文件大小 (字节)
    min_file_size INTEGER,               -- 最小文件大小 (字节)
    created_at TEXT DEFAULT CURRENT_TIMESTAMP, -- 任务创建时间 (ISO8601格式)
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP  -- 任务最后更新时间 (ISO8601格式)
);

CREATE TABLE IF NOT EXISTS backup_records (
    ID INTEGER PRIMARY KEY AUTOINCREMENT,     -- 记录唯一标识，自增主键
    task_id INTEGER NOT NULL,                 -- 关联的备份任务ID
    task_name TEXT NOT NULL,                  -- 关联的备份任务名称
    version_id TEXT NOT NULL UNIQUE,          -- 备份版本ID，唯一且非空
    backup_filename TEXT NOT NULL,            -- 备份文件名，非空
    backup_size INTEGER NOT NULL,             -- 备份文件大小 (字节)，非空
    status BOOLEAN NOT NULL,                  -- 备份状态(1:true/0:false)，非空
    failure_message TEXT,                     -- 备份失败时的错误信息
    checksum TEXT,                            -- 备份文件校验码
    storage_path TEXT NOT NULL,               -- 备份文件存放路径，非空
    created_at TEXT DEFAULT CURRENT_TIMESTAMP -- 备份完成时间 (ISO8601格式)
);

-- backup_tasks 表索引(显式)
CREATE INDEX IF NOT EXISTS idx_backup_tasks_name ON backup_tasks (name);

-- backup_records 表索引
CREATE INDEX IF NOT EXISTS idx_backup_records_created_at ON backup_records(created_at);
CREATE INDEX IF NOT EXISTS idx_backup_records_task_id ON backup_records (task_id); 
CREATE INDEX IF NOT EXISTS idx_backup_records_task_name ON backup_records (task_name);
`

// 固定的SQL更新语句
const updateBackupTaskSQL = `UPDATE backup_tasks SET
	retain_count = ?,
	retain_days = ?,
	compress = ?,
	include_rules = ?,
	exclude_rules = ?,
	max_file_size = ?,
	min_file_size = ?,
	updated_at = CURRENT_TIMESTAMP
WHERE ID = ?`

// UpdateTask 更新单个任务
//
// 参数:
//   - db: 数据库连接
//   - params: 包含所有更新参数的结构体
//
// 返回值:
//   - error: 更新失败时返回错误信息，否则返回 nil
func UpdateTask(db *sqlx.DB, params types.UpdateTaskParams) error {
	// 执行更新
	result, err := db.Exec(updateBackupTaskSQL,
		params.RetainCount,
		params.RetainDays,
		params.Compress,
		params.IncludeRules,
		params.ExcludeRules,
		params.MaxFileSize,
		params.MinFileSize,
		params.ID)

	if err != nil {
		return fmt.Errorf("执行更新失败: %w", err)
	}

	// 检查是否有行被更新
	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("获取更新结果失败: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("任务 ID: %d 未被更新", params.ID)
	}

	return nil
}

// InsertAddTaskConfig 将 AddTaskConfig 结构体的数据插入到 backup_tasks 表中。
// 它将 []string 类型的规则字段转换为 JSON 字符串进行存储。
//
// 参数：
//   - db：数据库连接对象
//   - cfg：要插入的 AddTaskConfig 结构体
//
// 返回值：
//   - error：如果插入过程中发生错误, 则返回非 nil 错误信息
func InsertAddTaskConfig(db *sqlx.DB, cfg *types.TaskConfig) error {
	// 验证配置
	if err := cfg.Validate(); err != nil {
		return fmt.Errorf("配置验证失败: %w", err)
	}

	// 处理包含规则
	includeRulesJSON, err := utils.MarshalRules(cfg.IncludeRules)
	if err != nil {
		return fmt.Errorf("编码包含规则失败: %w", err)
	}

	// 处理排除规则
	excludeRulesJSON, err := utils.MarshalRules(cfg.ExcludeRules)
	if err != nil {
		return fmt.Errorf("编码排除规则失败: %w", err)
	}

	// 存储目录(默认: ~/.bakctl)
	storageDir := cfg.StorageDir
	if storageDir == "" {
		storageDir = types.BackupDirPath
	}

	// 构建实际存储目录：存储目录 + 任务名称
	actualStorageDir := filepath.Join(storageDir, cfg.Name)

	// 将 AddTaskConfig 转换为 BackupTask, 处理规则字段的 JSON 编码
	backupTask := types.BackupTask{
		Name:         cfg.Name,         // 任务名称
		RetainCount:  cfg.RetainCount,  // 保留备份数量
		RetainDays:   cfg.RetainDays,   // 保留天数
		BackupDir:    cfg.BackupDir,    // 备份源目录
		StorageDir:   actualStorageDir, // 存储目录（存储目录 + 备份源目录名）
		Compress:     cfg.Compress,     // 是否压缩
		IncludeRules: includeRulesJSON, // 包含规则
		ExcludeRules: excludeRulesJSON, // 排除规则
		MaxFileSize:  cfg.MaxFileSize,  // 最大文件大小
		MinFileSize:  cfg.MinFileSize,  // 最小文件大小
	}

	// 执行插入操作
	_, err = db.NamedExec(insertBackupTaskQuery, backupTask)
	if err != nil {
		return fmt.Errorf("插入备份任务失败: %w", err)
	}

	return nil
}

// SQL INSERT 语句，使用命名参数
// 注意：created_at 和 updated_at 字段在表定义中通常有 DEFAULT CURRENT_TIMESTAMP，
// 所以这里不需要显式插入它们，数据库会自动处理。
const insertBackupTaskQuery = `
	INSERT INTO backup_tasks (
		name,
		retain_count,
		retain_days,
		backup_dir,
		storage_dir,
		compress,
		include_rules,
		exclude_rules,
		max_file_size,
		min_file_size
	) VALUES (
		:name,
		:retain_count,
		:retain_days,
		:backup_dir,
		:storage_dir,
		:compress,
		:include_rules,
		:exclude_rules,
		:max_file_size,
		:min_file_size
	)`

// SQL INSERT 语句，用于 backup_records 表
const insertBackupRecordQuery = `
	INSERT INTO backup_records (
		task_id,
		task_name,
		version_id,
		backup_filename,
		backup_size,
		status,
		failure_message,
		checksum,
		storage_path
	) VALUES (
		:task_id,
		:task_name,
		:version_id,
		:backup_filename,
		:backup_size,
		:status,
		:failure_message,
		:checksum,
		:storage_path
	)`

// InsertBackupRecord 将 BackupRecord 结构体的数据插入到 backup_records 表中。
//
// 参数：
//   - db：数据库连接对象
//   - rec：要插入的 BackupRecord 结构体
//
// 返回值：
//   - error：如果插入过程中发生错误，则返回非 nil 错误信息
func InsertBackupRecord(db *sqlx.DB, rec *types.BackupRecord) error {
	// 执行插入操作
	// sqlx 的 NamedExec 方法会根据结构体的 db 标签自动映射字段
	_, err := db.NamedExec(insertBackupRecordQuery, rec)
	if err != nil {
		return fmt.Errorf("插入备份记录失败: %w", err)
	}

	return nil
}
